package com.pengzy.comment.web.aop;

import cn.hutool.core.lang.Validator;
import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.pengzy.comment.core.annotation.ApiLog;
import com.pengzy.comment.core.utils.AddressUtils;
import com.pengzy.comment.core.utils.JsonUtils;
import com.pengzy.comment.mybatis.entity.SysLog;
import com.pengzy.comment.mybatis.mapper.SysLogMapper;
import com.pengzy.comment.web.result.ReactiveRequestContextHolder;
import com.pengzy.comment.web.result.ServletUtils;
import com.pengzy.comment.web.thread.ThreadPoolFactory;
import com.pengzy.comment.web.webFluxUtils.webFluxUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 操作日志记录处理
 *
 * @author skeqi
 */
@Aspect
@Component
@Slf4j(topic = "api-log")
public class LogAspect
{

    @Autowired
    private SysLogMapper sysLogMapper;

    @Resource
    private LogFilterExecutoServer logFilterExecutoServer;

    @Resource
    ReactiveRequestContextHolder reactiveRequestContextHolder;

    final static ExecutorService executorService= ThreadPoolFactory.getThreadPoolServer("AOP_LOG-",2);

    private final ThreadLocal<StopWatch> stopWatchThreadLocal = new ThreadLocal<StopWatch>();
    private final ThreadLocal<String> requestId = new ThreadLocal<>();


    @Pointcut("@annotation(com.pengzy.comment.core.annotation.ApiLog)")
    public void ApilogPointCut()
    {
    }

    @Pointcut("@annotation(com.pengzy.comment.core.annotation.LoginLog)")
    public void LoginlogPointCut()
    {
    }

    @Before(value = "ApilogPointCut()")
    public void befores(){
        requestId.set(IdWorker.getIdStr());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        stopWatchThreadLocal.set(stopWatch);
    }

    @AfterReturning(value = "ApilogPointCut()",returning = "var")
    public void afterReturning(JoinPoint joinPoint, Object var) {
        apiLog(joinPoint, null, var);
    }

    @AfterThrowing(pointcut = "ApilogPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,  Exception e) {
        apiLog(joinPoint, e, null);
    }



    protected  void apiLog(final JoinPoint joinPoint, final Exception es, Object methodResult){
        StopWatch stopwatch = stopWatchThreadLocal.get();
        try {
            ServletRequestAttributes sra = ServletUtils.getRequestAttributes();
            HttpServletRequest request = sra.getRequest();
            //获取全部Parameter参数
            Map<String, String[]> parameterMap = request.getParameterMap();
            // 获得注解
            ApiLog controllerLog = getAnnotationApiLog(joinPoint);
            if (controllerLog == null) {
                return;
            }
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            String className = joinPoint.getTarget().getClass().getName();
            Method method = ms.getMethod();
            String methodName=method.getName();
            String request_method=className+"."+methodName+"()";
            String params= Arrays.toString(joinPoint.getArgs());
            String returnStr= JsonUtils.toJsonString(methodResult);
            stopwatch.stop();
            double timeConsuming = stopwatch.getTotalTimeSeconds();
            double timeMins=stopwatch.getTotalTimeMillis();
            executorService.execute(()->this.logAdd(controllerLog,request,es,request_method,params,parameterMap,returnStr,timeMins));
            log.info("【请求ID】: {}" , requestId.get());
            log.info("【请求方法为】：{}" , request_method);
            log.info("【请求参数为(body)】：{}" , params);
            log.info("【请求参数为(param)】：{}",JsonUtils.toJsonString(parameterMap));
            log.info("【返回内容为】：{}" ,returnStr);
            log.info("【耗时(s)】:{}s -【耗时(ms)】:{}ms" ,timeConsuming,timeMins);
        }catch (Exception e){
            // 记录本地异常日志
            log.error("参数获取失败:{}", e.getMessage());
            e.printStackTrace();
        }finally {
            //executorService.shutdown();
            requestId.remove();
            stopWatchThreadLocal.remove();
        }
    }

    /**
     * 使用spring webflux
     * @param joinPoint
     * @param es
     * @param methodResult
     */
    protected  void apiLog2(final JoinPoint joinPoint, final Exception es, Object methodResult){
        StopWatch stopwatch = stopWatchThreadLocal.get();
        try {
            //Mono<ServerHttpRequest> request2 = reactiveRequestContextHolder.getRequest2();
            //Mono<ServerHttpRequest> request1 = reactiveRequestContextHolder.getRequest();
            ServerHttpRequest request3 = reactiveRequestContextHolder.getRequest3();
            //ServerHttpRequest serverHttpRequest = request1.toFuture().get();
            //获取全部Parameter参数
            Map<String, String[]> parameterMap = null;
            // 获得注解
            ApiLog controllerLog = getAnnotationApiLog(joinPoint);
            if (controllerLog == null) {
                return;
            }
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            String className = joinPoint.getTarget().getClass().getName();
            Method method = ms.getMethod();
            String methodName = method.getName();
            String request_method = className + "." + methodName + "()";
            String params = Arrays.toString(joinPoint.getArgs());
            String returnStr = JsonUtils.toJsonString(webFluxUtil.getMonoValue(methodResult));
            stopwatch.stop();
            double timeConsuming = stopwatch.getTotalTimeSeconds();
            double timeMins = stopwatch.getTotalTimeMillis();
            this.logAdd(controllerLog, request3, es, request_method, params, parameterMap, returnStr, timeMins);
            log.info("【请求ID】: {}", requestId.get());
            log.info("【请求方法为】：{}", request_method);
            log.info("【请求参数为(body)】：{}", params);
            log.info("【请求参数为(param)】：{}", request3.getQueryParams().toString());
            log.info("【返回内容为】：{}", returnStr);
            log.info("【耗时(s)】:{}s -【耗时(ms)】:{}ms", timeConsuming, timeMins);
        }catch (Exception e){
            // 记录本地异常日志
            log.error("参数获取失败:{}", e.getMessage());
            e.printStackTrace();
        }finally {
            //executorService.shutdown();
            requestId.remove();
            stopWatchThreadLocal.remove();
        }
    }

    public void logAdd(ApiLog controllerLog,ServerHttpRequest request, final Exception es,
                       String request_method,String params,Map<String, String[]> parameterMap,String returnStr, double timeMins){
        SysLog sysLog=new SysLog();
        sysLog.setUsername("pengzy");
        sysLog.setType(controllerLog.type().getInfo());
        sysLog.setOperation(controllerLog.method());
        String ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
        sysLog.setIp(ip);
        sysLog.setRequest(request.getMethodValue());
        sysLog.setStatus("OK");
        if(es!=null){
            sysLog.setStatus("NO");
            String msg=es.getMessage();
            sysLog.setErrorMsg(msg!=null&&msg.length()>2000?msg.substring(0,2000):msg);
        }
        sysLog.setDict(AddressUtils.getRealAddressByIP(sysLog.getIp()));
        sysLog.setRequestDict(request.getURI().getPath());
        sysLog.setMethod(request_method);
        sysLog.setParam(request.getQueryParams().toString());
        sysLog.setParam(params);
        if(StringUtils.isBlank(params)){
            sysLog.setParam(JsonUtils.toJsonString(parameterMap));
        }
        sysLog.setResponse(returnStr);
        sysLog.setTime(timeMins+"(ms)");
        sysLog.setCreateTime(LocalDateTime.now());
        logFilterExecutoServer.init(sysLog);
    }

    /**
     * 操作日志
     * @param controllerLog
     * @param request
     * @param es
     * @param request_method
     * @param params
     * @param parameterMap
     * @param returnStr
     * @param timeMins
     */
    public void logAdd(ApiLog controllerLog, HttpServletRequest request,final Exception es,
                       String request_method,String params,Map<String, String[]> parameterMap,String returnStr, double timeMins){
        SysLog sysLog=new SysLog();
        sysLog.setUsername("pengzy");
        sysLog.setType(controllerLog.type().getInfo());
        sysLog.setOperation(controllerLog.method());
        sysLog.setIp(ServletUtil.getClientIP(request));
        sysLog.setRequest(request.getMethod());
        sysLog.setStatus("OK");
        if(es!=null){
            sysLog.setStatus("NO");
            String msg=es.getMessage();
            sysLog.setErrorMsg(msg!=null&&msg.length()>2000?msg.substring(0,2000):msg);
        }
        sysLog.setDict(AddressUtils.getRealAddressByIP(sysLog.getIp()));
        sysLog.setRequestDict(request.getRequestURI());
        sysLog.setMethod(request_method);
        sysLog.setParam(params);
        if(StringUtils.isBlank(params)){
            sysLog.setParam(JsonUtils.toJsonString(parameterMap));
        }
        sysLog.setResponse(returnStr);
        sysLog.setTime(timeMins+"(ms)");
        sysLog.setCreateTime(LocalDateTime.now());
        sysLogMapper.insert(sysLog);
       // logFilterExecutoServer.init(sysLog);
    }





    /**
     * 插入数据库
     */
    public void add(){

    }


    private ApiLog getAnnotationApiLogs(ProceedingJoinPoint joinPoint) throws Exception
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(ApiLog.class);
        }
        return null;
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private ApiLog getAnnotationApiLog(JoinPoint joinPoint)
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(ApiLog.class);
        }
        return null;
    }
    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray)
    {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0)
        {
			for (Object o : paramsArray) {
				if (Validator.isNotNull(o) && !isFilterObject(o)) {
					params.append(JsonUtils.toJsonString(o)).append(" ");
				}
			}
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o)
    {
        Class<?> clazz = o.getClass();
        if (clazz.isArray())
        {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        }
        else if (Collection.class.isAssignableFrom(clazz))
        {
            Collection collection = (Collection) o;
			for (Object value : collection) {
				return value instanceof MultipartFile;
			}
        }
        else if (Map.class.isAssignableFrom(clazz))
        {
            Map map = (Map) o;
			for (Object value : map.entrySet()) {
				Map.Entry entry = (Map.Entry) value;
				return entry.getValue() instanceof MultipartFile;
			}
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }



    /**
     * 获取异常返回参数
     * @param e
     * @return
     */
//    public String returnIng(Exception e){
//        if(e instanceof CilentProductionException){
//            CilentProductionException ces=(CilentProductionException)e;
//            return JsonUtils.toJsonString(AjaxResult.error(ces.getCode(), ces.getMessage(),ces.getData()));
//        }
//        if(e instanceof DuplicateKeyException){
//            DuplicateKeyException ces=(DuplicateKeyException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(ces.getCause().getMessage()));
//        }
//        if(e instanceof NoHandlerFoundException){
//            NoHandlerFoundException ces=(NoHandlerFoundException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(HttpStatus.HTTP_NOT_FOUND,"路径不存在，请检查路径是否正确"));
//        }
//        if(e instanceof AccessDeniedException){
//            AccessDeniedException ces=(AccessDeniedException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(HttpStatus.HTTP_FORBIDDEN,"没有权限，请联系管理员授权"));
//        }
//        if(e instanceof AccountExpiredException){
//            AccountExpiredException ces=(AccountExpiredException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(ces.getMessage()));
//        }
//        if(e instanceof UsernameNotFoundException){
//            UsernameNotFoundException ces=(UsernameNotFoundException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(ces.getMessage()));
//        }
//        if(e instanceof DataIntegrityViolationException){
//            DataIntegrityViolationException ces=(DataIntegrityViolationException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(ces.getCause().getMessage()));
//        }
//        if(e instanceof IOException){
//            //IOException ces=(IOException)e;
//            return  JsonUtils.toJsonString("请求异常，请稍后再试！");
//        }
//        if(e instanceof HttpMessageNotReadableException){
//            //DataIntegrityViolationException ces=(DataIntegrityViolationException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error("请求参数缺失，格式按照application/json"));
//        }
//        if(e instanceof BindException){
//            BindException ces=(BindException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(ces.getAllErrors().get(0).getDefaultMessage()));
//        }
//        if(e instanceof ConstraintViolationException){
//            ConstraintViolationException ces=(ConstraintViolationException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(ces.getConstraintViolations().iterator().next().getMessage()));
//        }
//        if(e instanceof MethodArgumentNotValidException){
//            MethodArgumentNotValidException ces=(MethodArgumentNotValidException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error(1000,ces.getBindingResult().getFieldError().getDefaultMessage()));
//        }
//        if(e instanceof DemoModeException){
//            DemoModeException ces=(DemoModeException)e;
//            return  JsonUtils.toJsonString(AjaxResult.error("演示模式，不允许操作"));
//        }
//        return  JsonUtils.toJsonString(AjaxResult.error("服务器开小差，请稍后再试！"));
//    }
}
